/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//expr_dot.hpp

#ifndef MVL_EXPR_DOT_HPP
#define MVL_EXPR_DOT_HPP

#include "layout.hpp"
#include "expr_order.hpp"

namespace mvl {

template<typename E1, typename E2>
struct mat_mat_dot 
{
    //choose expr1's order
    //make sure expr2 has the same order
    typedef E1                                  expr1_t;
    typedef typename expr1_t::order             order;
    typedef typename expr1_t::layout_t          layout1_t;
    typedef typename expr1_t::const_closure_t   expr1_closure_t;

    typedef const expr_order<order, const E2>   expr2_t;
    typedef typename expr2_t::layout_t          layout2_t;
    typedef typename expr2_t::const_closure_t   expr2_closure_t;

    typedef typename promote_traits<
        typename expr1_t::value_type,
        typename expr2_t::value_type
    >::promote_t value_type;

    template<typename PrevTag, typename Tag1, typename Tag2> struct compute_tag : 
        layout::add_tags<
            PrevTag,
            typename layout::mul_tags<
                Tag1,
                Tag2
            >::type
        > { };

    template<typename Entry1, typename Entry2, typename CurrentTag, typename Tag1, typename Tag2>
    struct entry { };

    struct element_dot {
        element_dot(expr1_t const& e1, expr2_t const& e2, value_type &sum): m_e1(e1), m_e2(e2), m_sum(sum) {  }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_v_, layout::_v_>) {
            m_sum += m_e1.template at<Entry1>() * m_e2.template at<Entry2>();
        }

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_1_, layout::_v_>) { 
            m_sum += m_e2.template at<Entry2>();  
        };

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_v_, layout::_1_>) { 
            m_sum += m_e1.template at<Entry1>();   
        };

        template<typename Entry1, typename Entry2, typename CurrentTag>
        void operator()(entry<Entry1, Entry2, CurrentTag, layout::_1_, layout::_1_>) { 
            m_sum += value_type(1);   
        };

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_v_, layout::_v_>) {
            m_sum = m_e1.template at<Entry1>() * m_e2.template at<Entry2>();
        };

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_1_, layout::_v_>) { 
            m_sum = m_e2.template at<Entry2>();  
        };

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_v_, layout::_1_>) { 
            m_sum = m_e1.template at<Entry1>();   
        };

        template<typename Entry1, typename Entry2>
        void operator()(entry<Entry1, Entry2, layout::_0_, layout::_1_, layout::_1_>) { 
            m_sum = value_type(1);   
        };

        expr1_t const& m_e1;
        expr2_t const& m_e2;
        value_type &m_sum;
    };

    struct row_dot {
        row_dot(expr1_t const& e1, expr2_t const& e2, value_type &sum) : m_e1(e1), m_e2(e2), m_sum(sum) {}
        expr1_t const& m_e1;
        expr2_t const& m_e2;
        value_type &m_sum;

        template<typename Row>
        void operator()(Row) {
            meta::for_each<
                typename meta::begin<Row>::type,
                typename meta::end<Row>::type
            >(element_dot(m_e1, m_e2, m_sum));
        }
    };

    template<typename Data, typename It1, typename It2, typename CurrentTag, typename Condition>
    struct state {
        typedef Data        data;
        typedef It1         it1;
        typedef It2         it2;
        typedef CurrentTag  current_tag;
        typedef Condition   condition;
        typedef state       type;
    };

    template<typename S>
    struct mat_mat_dot1 {
        typedef typename layout::begin2<typename S::it1>::type begin1;
        typedef typename layout::begin2<typename S::it2>::type begin2;
        typedef typename layout::end2<typename S::it1>::type end1;
        typedef typename layout::end2<typename S::it2>::type end2;

        struct update {
            template<typename State>
            struct apply {
                typedef typename meta::deref<typename State::it1>::type e1;
                typedef typename meta::deref<typename State::it2>::type e2;
                typedef typename layout::get2<typename e1::i, typename e1::j, order>::type j1;
                typedef typename layout::get2<typename e2::i, typename e2::j, order>::type j2;
                typedef typename meta::eval_if<
                    typename meta::less_equal<j1, j2>::type,
                    meta::next<typename State::it1>,
                    meta::identity<typename State::it1>
                >::type new_it1;

                typedef typename meta::eval_if<
                    typename meta::less_equal<j2, j1>::type,
                    meta::next<typename State::it2>,
                    meta::identity<typename State::it2>
                >::type new_it2;

                typedef typename meta::and_<
                    typename meta::not_equal_to<new_it1, end1>::type,
                    typename meta::not_equal_to<new_it2, end2>::type
                >::type new_condition;

                typedef typename meta::eval_if<
                    typename meta::equal_to<j1, j2>::type,
                    compute_tag<
                        typename State::current_tag, 
                        typename e1::tag,
                        typename e2::tag
                    >,
                    meta::identity<typename State::current_tag>
                >::type new_tag;

                typedef typename meta::eval_if<
                    typename meta::equal_to<j1, j2>::type,
                    typename meta::push_back<
                        typename State::data,
                        entry<
                            e1,
                            e2,
                            typename State::current_tag,
                            typename e1::tag,
                            typename e2::tag
                        >
                    >,
                    meta::identity<typename State::data>
                >::type new_data;

                typedef state<new_data, new_it1, new_it2, new_tag, new_condition> type;
            };
        };

        typedef typename meta::while_<
            state<
                meta::vector<>,
                begin1,
                begin2,
                typename S::current_tag,
                typename meta::and_<
                    typename meta::not_equal_to<begin1, end1>::type,
                    typename meta::not_equal_to<begin2, end2>::type
                >::type
            >,
            update
        >::type type;
    };

    typedef typename layout::begin1<layout1_t>::type begin1;
    typedef typename layout::begin1<layout2_t>::type begin2;
    typedef typename layout::end1<layout1_t>::type end1;
    typedef typename layout::end1<layout2_t>::type end2;

    struct update {
        template<typename State>
        struct apply {
            typedef typename meta::next<typename State::it1>::type new_it1;
            typedef typename meta::next<typename State::it2>::type new_it2;
            typedef typename mat_mat_dot1<State>::type new_state1;
            typedef typename meta::push_back<
                typename State::data,
                typename new_state1::data
            >::type new_data;
            typedef state<
                new_data, 
                new_it1, 
                new_it2,
                typename new_state1::current_tag,
                typename meta::and_<
                    typename meta::not_equal_to<new_it1, end1>::type,
                    typename meta::not_equal_to<new_it2, end2>::type
                >::type
            > type;
        };
    };

    static value_type apply(expr1_t const& e1, expr2_t const& e2) {
        typedef typename meta::while_<
            state<
                meta::vector<>,
                begin1,
                begin2,
                layout::_0_,
                typename meta::and_<
                    typename meta::not_equal_to<begin1, end1>::type,
                    typename meta::not_equal_to<begin2, end2>::type
                >::type
            >,
            update
        >::type::data entries; 

        value_type sum(0);
        meta::for_each<
            typename meta::begin<entries>::type,
            typename meta::end<entries>::type
        >(row_dot(e1, e2, sum));
        return sum;          
    }

};


template<typename E1, typename E2>
inline 
typename promote_traits<typename E1::value_type, typename E2::value_type>::promote_t
dot(expr<E1> const& e1, expr<E2> const& e2)
{
    MVL_STATIC_CHECK((E1::Rows::value == E2::Rows::value), "mat_size_mismatch");
    MVL_STATIC_CHECK((E1::Cols::value == E2::Cols::value), "mat_size_mismatch");
    return mat_mat_dot<E1, E2>::apply(e1(), e2());
}

} // namespace mvl

#endif
